package pbservice

import "viewservice"
import "net/rpc"

import "crypto/rand"
import (
	"math/big"
	"strings"
	"time"
)

type Clerk struct {
	vs *viewservice.Clerk
	// Your declarations here
	tmpPrimary string
	viewnum    uint
}

// this may come in handy.
func nrand() int64 {
	max := big.NewInt(int64(1) << 62)
	bigx, _ := rand.Int(rand.Reader, max)
	x := bigx.Int64()
	return x
}

func MakeClerk(vshost string, me string) *Clerk {
	ck := new(Clerk)
	ck.vs = viewservice.MakeClerk(me, vshost)
	// Your ck.* initializations here
	ck.tmpPrimary = ""
	ck.viewnum = 0
	return ck
}

//
// call() sends an RPC to the rpcname handler on server srv
// with arguments args, waits for the reply, and leaves the
// reply in reply. the reply argument should be a pointer
// to a reply structure.
//
// the return value is true if the server responded, and false
// if call() was not able to contact the server. in particular,
// the reply's contents are only valid if call() returned true.
//
// you should assume that call() will return an
// error after a while if the server is dead.
// don't provide your own time-out mechanism.
//
// please use call() to send all RPCs, in client.go and server.go.
// please don't change this function.
//
func call(srv string, rpcname string,
	args interface{}, reply interface{}) bool {
	c, errx := rpc.Dial("unix", srv)
	if errx != nil {
		return false
	}
	defer c.Close()

	err := c.Call(rpcname, args, reply)
	if err == nil {
		return true
	}

	//fmt.Println(err)
	return false
}

//
// fetch a key's value from the current primary;
// if they key has never been set, return "".
// Get() must keep trying until it either the
// primary replies with the value or the primary
// says the key doesn't exist (has never been Put().
//
func (ck *Clerk) Get(key string) string {

	// Your code here.

	args := &GetArgs{}
	var reply GetReply
	args.Key = key
	for {
		if call(ck.getPrimary(), "PBServer.Get", args, &reply); reply.Viewnum != 0 {
			ck.checkPrimary(reply.Viewnum)
			break
		}
		ck.updatePrimary()
		time.Sleep(time.Duration(viewservice.PingInterval))
	}
	return reply.Value
}

//
// send a Put or Append RPC
//
func (ck *Clerk) PutAppend(key string, value string, op string) {

	// Your code here.
	args := &PutAppendArgs{}
	args.Key = key
	args.IsPut = strings.EqualFold(op, "PUT")
	args.Value = value
	args.FromPrimary = false
	args.Number = nrand()

	var reply PutAppendReply
	//log.Println("[client]", "put:", key)
	for !call(ck.getPrimary(), "PBServer.PutAppend", args, &reply) {
		ck.updatePrimary()
		time.Sleep(time.Duration(viewservice.PingInterval))
		//log.Println("[client]", "put:", key, "failed. retry", ck.getPrimary())
	}
	ck.checkPrimary(reply.Viewnum)
}

func (ck *Clerk) getPrimary() string {

	if ck.tmpPrimary == "" {
		//log.Println("[client]","empty.update primary")
		ck.updatePrimary()
	}
	return ck.tmpPrimary
}

func (ck *Clerk) checkPrimary(num uint) {
	if num > ck.viewnum {
		//log.Println("[client]","small num.update primary")
		ck.updatePrimary()
		if ck.getPrimary() != "" {
			ck.viewnum = num
		}
	}
}
func (ck *Clerk) updatePrimary() bool {
	view, err := ck.vs.Get()
	if !err || view.Primary == "" {
		return false
	}
	ck.tmpPrimary = view.Primary
	ck.viewnum = view.Viewnum
	return true
}

//
// tell the primary to update key's value.
// must keep trying until it succeeds.
//
func (ck *Clerk) Put(key string, value string) {
	ck.PutAppend(key, value, "Put")
}

//
// tell the primary to append to key's value.
// must keep trying until it succeeds.
//
func (ck *Clerk) Append(key string, value string) {
	ck.PutAppend(key, value, "Append")
}
